/*
 * 代表一个待爬的一类页面，从这一类页面爬出的数据成为“数据表”，
 * 每一页的数据成为“数据行”或“一行数据”
 */
var SourcePage = function(db, sitemap){
	this.id = null;
	this.name = null;
	this.link = null;
	this.rowElement = null;
	this.paging = null;
	this.fields = []; //数据表结构（即字段，不包括连接页面的字段）
	this.linkedPages = []; //连接页面，比如“详情页面”
	if(db){
		this.id = db.id;
		this.name = db.name;
		this.link = typeof db.link == 'string' ? db.link : 
						new PapaElement(db.link, sitemap); //页面的URL或能获取URL的选择器
		this.rowElement = db.rowElement; //数据行的元素或选择器
		this.paging = new Paging(db.paging, sitemap); //翻页策略
		for (var i = 0; i < db.fields.length; i++) {
			this.fields.push(new PapaField(db.fields[i], sitemap));
		}
		for (var i = 0; i < db.linkedPages.length; i++) {
			this.linkedPages.push(new LinkedPage(db.linkedPages[i], sitemap));
		}
	}
	
	this.toDB = function(){
		var db_s = {
			id: this.id,
			name: this.name,
			link: this.link,
			rowElement: this.rowElement.id
		}
		if(this.paging){
			db_s.paging = this.paging.toDB();
		}
		db_s.fields = [];
		for (var i = 0; i < this.fields.length; i++) {
			db_s.fields.push(this.fields[i].toDB());
		}
		
		db_s.linkedPages = [];
		for (var i = 0; i < this.linkedPages.length; i++) {
			db_s.linkedPages.push(this.linkedPages[i].toDB());
		}
		return db_s;
	}
}

/*
 * 内容提取器。
 * 根据选择器提取文本（text），属性（attr），或者自定义方法（method）从元素（Element）中提取相关的信息
 */
var ContentExtractor = function(db){
	this.type = null;
	this.attr = null;
	this.process = null; //拿到提取的内容后的处理
	if(db){
		this.type = db.type;
		this.attr = db.attr;
		this.process = db.process; //拿到提取的内容后的处理
	}
	
	this.getValue = function(ele){
		var r = null;
		if(this.type == 'attr'){
			r = ele.attr(this.attr);
		}
		else{
			r = ele.text();
		}
		if(this.process){
			var m = eval("(function _$_$(){ return ("+ this.process +")})");
			r = m.call(r);
		}
		if(r){
			r = r.trim();
		}
		return r;
	}
	this.toString = function(){
		if(this.type == 'text'){
			return 'text{' + this.process + '}';
		}
		if(this.type == 'attr'){
			return 'attr:' + this.attr + '{' + this.process + '}';
		}
	}
	this.toDB = function(){
		return {
			type: this.type,
			attr: this.attr,
			process: this.process
		}
	}
}
ContentExtractor.types = ['text', 'attr'];

/*
 * 代表一个删选器所能筛选到的一个或多个标签元素
 */
var PapaElement = function(db, sitemap){
	this.id = null;
	this.name = null;
	this.selector = null;
	this.contentExtractor = null;
	this.parentElement = null;
	this.row = false; //if rowElement. transient
	
	this.elements = null; //jquery element

	if(db){
		this.id = db.id;
		this.name = db.name;
		this.selector = db.selector; //String
		this.contentExtractor = db.contentExtractor ? 
				new ContentExtractor(db.contentExtractor) : null;
		if(db.parentElement){
			if(typeof db.parentElement == 'number'){
				this.parentElement = sitemap.getSelector(db.parentElement);
			}else{
				this.parentElement = new PapaElement(db.parentElement); //PapaElement
			}
		}
	}
	
	//topParent 元素选择器的顶层元素，比如每个字段元素的顶层元素是行元素
	this.select = function(topParent){
		if(topParent || !this.elements){
			if(this.parentElement){
				this.elements = this.parentElement.find(this.selector);
			}
			else if(topParent){
				this.elements = topParent.find(this.selector);
			}
			else{
				this.elements = $(this.selector);
			}
		}
		return this.elements;
	};
	
	this.getValue = function(topParent){
		return this.contentExtractor.getValue(this.select(topParent));
	};
	
	this.toString = function(){
		var a = [];
		var ele = this;
		while(true){
			a.push(ele.selector);
			ele = ele.parentElement;
			if(!ele){
				break;
			}
		}
		return a.reverse().join(' ');
	};
	
	this.toDB = function(){
		var db_s = {
			id: this.id,
			name: this.name,
			selector: this.selector
		};
		if(this.parentElement){
			//papaElements是全局变量 
			db_s.parentElement = this.parentElement.id;
		}
		var ce = this.contentExtractor;
		if(ce){
			db_s.contentExtractor = ce.toDB();
		}
		return db_s;
	};
};

/*
 * 分页策略
 * 1）根据分页链接中的URL分页
 * 2）下拉滚动条加载分批数据（Ajax）
 * 3）Click事件分页（Ajax）
 */
var Paging = function(db, sitemap){
	this.type = null;
	this.nextPageElement = null;
	
	if(db){
		this.type = db.type;
		this.nextPageElement = new PapaElement(db.nextPageElement, sitemap); 
	}
	
	this.nextPage = function(topElement){
		var ele = this.nextPageElement.select(topElement);
		if(ele.length){
			if(this.type == 'url'){
				return ele[0].href;
			}
			else if(this.type == 'scroll'){
				var height = ele.height();
				var top = 200;
				while(true){
					if(top < height){
						ele.scrollTop(top);
						top += 200;
					}else{
						ele.scrollTop(height);
						break;
					}
				}
			}
			else if(this.type == 'click'){
				ele.click();
			}
		}
	};
	
	this.toDB = function(){
		return {
			type: this.type,
			nextPageElement: this.nextPageElement && this.nextPageElement.toDB()
		};
	};
};
Paging.types = ['url', 'scroll', 'click'];

/*
 * 相当于数据库字段数据（title是字段名，getValue获取字段值）
 */
var PapaField = function(db, sitemap){
	this.title = null;
	this.element = null;
	this.unique = false;
	
	if(db){
		this.title = db.title;
		this.element = new PapaElement(db.element, sitemap);
		this.unique = db.unique;
	}
	
	this.getValue = function(rowElement){
		return this.element.getValue(rowElement);
	};
	
	this.toDB = function(){
		var db_f = {
			title: this.title,
			unique: this.unique,
			element: this.element.toDB()
		};
		return db_f;
	};
};

/*
 * 通常指SourcePage链接出的详情页面，paging指示link策略，不是只分页。
 */
var LinkedPage = function(db, sitemap){
	this.paging = null;
	this.sourcePage = null;
	
	if(db){
		this.paging = new Paging(db.paging);
		this.sourcePage = db.sourcePage;
	}
	
	//deprecated
//	this.getLink = function(topElement){
//		var ele = this.nextPageElement.select(topElement);
//		return ele[0].href;
//	}
	
	this.toDB = function(){
		return {
			paging: this.paging.toDB(),
			//sourcePages是全局变量 
			sourcePage: this.sourcePage.id
		};
	};
};

var Sitemap = function(db){
	this.name = 'empty';
	this.selectors = [];
	this.pages = [];
	
	this.getSelector = function(id){
		return this.selectors.find(function(o){return o.id == id})
	}
	this.getPage = function(id){
		return this.pages.find(function(o){return o.id == id})
	}
	
	if(db){
		this.name = db.name;
		
		var db_sel = db.selectors || [];
		for (var i = 0; i < db_sel.length; i++) {
			this.selectors.push(new PapaElement(db_sel[i], this));
		}
		//link
		for (var i = 0; i < this.selectors.length; i++) {
			var e = this.selectors[i];
			if(typeof e.parentElement == 'number'){
				var index = e.parentElement;
				e.parentElement = this.selectors[index];
			}
		}
		
		//SourcePage
		var db_sps = db.pages || [];
		for (var i = 0; i < db_sps.length; i++) {
			this.pages.push(new SourcePage(db_sps[i], this));
		}
		//link
		var pages = this.pages;
		var selectors = this.selectors;
		pages.forEach(function(page){
			page.linkedPages.forEach(function(lp){
				if(typeof lp.sourcePage == 'number'){
					lp.sourcePage = pages.find(function(_p){
						return _p.id == lp.sourcePage;
					});
				}
			})
			//link rowElement
			if(typeof page.rowElement == 'number'){
				var rowEleId = page.rowElement;
				if(rowEleId){
					page.rowElement = selectors.find(function(s){return s.id == rowEleId});
					page.rowElement.row = true; //flag
				}
			}
		})
	}
	
	this.toDB = function(){
		var db = {};
		//PapaElement
		var sel = this.selectors || [];
		var db_sel = [];
		for (var i = 0; i < sel.length; i++) {
			var s = sel[i];
			db_sel.push(s.toDB());
		}
		db.selectors = db_sel;
		
		//SourcePage
		var sps = this.pages || [];
		var db_sps = [];
		for (var i = 0; i < sps.length; i++) {
			var s = sps[i];
			db_sps.push(s.toDB());
		}
		db.pages = db_sps;
		db.name = this.name;
		return db;
	},
	
	this.nextSelectorId = function(){
		if(this.selectors.length == 0){
			return 1;
		}else{
			return this.selectors[this.selectors.length - 1].id + 1;
		}
	}
	
	this.nextPageId = function(){
		if(this.pages.length == 0){
			return 1;
		}else{
			return this.pages[this.pages.length - 1].id + 1;
		}
	}
}

